
package com.hulk.ratel.web.exceptionhandler;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Throwables;
import com.hulk.ratel.common.constant.enums.EC;
import com.hulk.ratel.common.exception.BaseRteException;
import com.hulk.ratel.common.util.SpringContextHolder;
import com.hulk.ratel.manage.commonutil.ContentUtil;
import com.hulk.ratel.api.web.dto.HeadReqDTO;
import com.hulk.ratel.api.web.dto.HeadRespDTO;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.joda.time.DateTime;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.yaml.snakeyaml.constructor.DuplicateKeyException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;


/**
 * @author hulk
 */


@Slf4j
@ControllerAdvice(basePackages = "com.hulk.ratel")
public class GlobalExceptionHandler {



	@ExceptionHandler(MethodArgumentNotValidException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,MethodArgumentNotValidException e) {
		log.error("参数验证失败:[{}]", e.getMessage());
		FieldError error = e.getBindingResult().getFieldError();
		String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
		processException(req, resp,EC.E_100016.getErrCode(),EC.E_100016.getErrDesc(),e) ;
	}
	@ExceptionHandler(BindException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,BindException e) {
		log.error("参数绑定失败:[{}]", e.getMessage());
		FieldError error = e.getBindingResult().getFieldError();
		String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
		processException(req, resp,EC.E_100016.getErrCode(),EC.E_100016.getErrDesc(),e) ;
	}
	@ExceptionHandler(ConstraintViolationException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,ConstraintViolationException e) {
		log.error("参数验证失败:[{}]", e.getMessage());
		ConstraintViolation<?> violation = e.getConstraintViolations().iterator().next();
		String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
		String message = String.format("%s:%s", path, violation.getMessage());
		processException(req, resp,EC.E_100016.getErrCode(),EC.E_100016.getErrDesc(),e) ;
	}
	@ExceptionHandler(MissingServletRequestParameterException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,MissingServletRequestParameterException e) {
		log.error("缺少请求参数:[{}]", e.getMessage());
		String message = String.format("缺少必要的请求参数: %s", e.getParameterName());
		processException(req, resp,EC.E_100016.getErrCode(),EC.E_100016.getErrDesc(),e);
	}
	@ExceptionHandler(MethodArgumentTypeMismatchException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,MethodArgumentTypeMismatchException e) {
		log.error("请求参数格式错误:[{}]", e.getMessage());
		String message = String.format("请求参数格式错误: %s", e.getName());
		processException(req, resp,EC.E_100016.getErrCode(),EC.E_100016.getErrDesc(),e);
	}
	@ExceptionHandler(DuplicateKeyException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,DuplicateKeyException e) {
		log.error("数据库行字段重复:[{}]", e.getMessage());
		processException(req, resp,EC.E_200106.getErrCode(),EC.E_200106.getErrDesc(),e) ;
	}
	@ExceptionHandler(HttpMessageNotReadableException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,HttpMessageNotReadableException e) {
		log.error("消息不能读取:[{}]", e.getMessage());
		processException(req, resp,EC.E_916003.getErrCode(),EC.E_916003.getErrDesc(),e);
	}
	@ExceptionHandler(NoHandlerFoundException.class)
	@ResponseStatus(HttpStatus.NOT_FOUND)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,NoHandlerFoundException e) {
		log.error("404没找到请求:[{}]", e.getMessage());
		processException(req, resp,EC.E_914004.getErrCode(),EC.E_914004.getErrDesc(),e);
	}
	@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
	@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,HttpRequestMethodNotSupportedException e) {
		log.error("405不支持当前请求方法:[{}]", e.getMessage());
		processException(req, resp,EC.E_914005.getErrCode(),EC.E_914005.getErrDesc(),e);
	}
	@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
	@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,HttpMediaTypeNotSupportedException e) {
		log.error("不支持当前媒体类型:[{}]", e.getMessage());
		processException(req, resp,EC.E_914015.getErrCode(),EC.E_914015.getErrDesc(),e);
	}
	@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
	@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
	public void handleError(HttpServletRequest req, HttpServletResponse resp,HttpMediaTypeNotAcceptableException e) {
		String message = e.getMessage() + " " + StringUtils.collectionToCommaDelimitedString(e.getSupportedMediaTypes());
		log.error("不接受的媒体类型:[{}]", message);
		processException(req, resp,EC.E_914015.getErrCode(),EC.E_914015.getErrDesc(),e);
	}
	@ExceptionHandler(MyBatisSystemException.class)
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
	public void handleError( HttpServletRequest req, HttpServletResponse resp, MyBatisSystemException e) {
		log.error("持久层异常:[{}]", e.getMessage());
		processException(req, resp,EC.E_915000.getErrCode(),EC.E_915000.getErrDesc(),e);
	}

    @ExceptionHandler(value = BaseRteException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public void handleError(HttpServletRequest req, HttpServletResponse resp, BaseRteException e)  {
		log.error("业务异常:[{}]", e.getMessage());
        processException(req, resp, e.getErrCode(), e.getMessage(), e);
    }

    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.EXPECTATION_FAILED)
    public void handleError(HttpServletRequest req, HttpServletResponse resp, Exception e)  {
		log.error("未知异常:[{}]", e.getMessage());
        processException(req, resp, EC.E_999999.getErrCode(), EC.E_999999.getErrDesc(), e);
    }

    @SneakyThrows
    public static  void    processException(HttpServletRequest req, HttpServletResponse resp, int errorCode, String errorMsg, Throwable e)  {

        String workId = (String) req.getAttribute("workId");
        String txnCode = (String) req.getAttribute("txnCode");
        String partnerNo = (String) req.getAttribute("partnerNo");
        String traceId = (String) req.getAttribute("traceId");
        log.error(" workId:{}, traceId:{}, partnerNo:{}, txnCode:{},  errorCode:{},errorMsg:{},e:{}", new Object[]{
                workId, traceId, partnerNo, txnCode, errorCode, errorMsg, Throwables.getStackTraceAsString(e)});
        String partnerAESKey = (String) req.getAttribute("partnerAESKey");
        String requestHead = (String) req.getAttribute("requestHead");
        HeadReqDTO reqHead =   JSONObject.parseObject(requestHead, HeadReqDTO.class);
        HeadRespDTO respHead = new HeadRespDTO();
        BeanUtil.copyProperties(reqHead, respHead);
        respHead.setTraceId(traceId);
        respHead.setPartnerNo(partnerNo);
        respHead.setTxnCode(txnCode);
        respHead.setWorkId(workId);
        DateTime now = DateTime.now();
        respHead.setRespDate(now.toString("yyyyMMdd"));
        respHead.setRespTime(now.toString("yyyyMMddHHmmss"));
        respHead.setRespCode(errorCode);
        respHead.setRespMsg(errorMsg);
        ContentUtil   contentUtil = SpringContextHolder.getBean("contentUtil");
        String respJson ;
        try {
            respJson = contentUtil.postHandle(respHead, partnerAESKey, partnerNo, workId);
        } catch (Exception e1) {
           log.error("",e1);
            respJson =   JSONObject.toJSONString(respHead);
        }
        resp.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        resp.setCharacterEncoding(StandardCharsets.UTF_8.name());
        PrintWriter wr = resp.getWriter();
        wr.write(respJson);
        wr.flush();
        wr.close();
        //return contentUtil.postHandle(respHead,partnerAESKey, partnerNo, workId);
    }

    @ModelAttribute
    public void addAttribute(Model m) {
        log.debug("============应用到所有@RequestMapping注解方法，在其执行之前把返回值放入Model");
    }

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        log.debug("============应用到所有@RequestMapping注解方法，在其执行之前初始化数据绑定器");
    }

}
